package gnssio

import (
	"dgo/goutils/dnet"
	"dgo/goutils/gis"
	"dgo/goutils/gnss"
	"dgo/goutils/utils"
	"fmt"
	"gitee.com/ymofen/supervalue"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"
)

type CommNtripClient struct {
	flag      byte // 1: 激活服务, 0:关闭服务
	client    *gnss.NtripClient
	listeners *utils.SafeList
	nmea      *gis.NMEA
	taskTimer *time.Timer

	config_rand_m              float64
	config_lng_0, config_lat_0 float64
	config_height_0            float64
	config_change_T            time.Time
	config_start               time.Duration
	config_end                 time.Duration
	config_rand_sec            int
	config_online_m            int64 // 在线分钟
	config_offline_m           int64 // 离线分钟
	config_timesection         *utils.MultiTimeSection

	config_gen *gis.GenPoint
}

func NewCommNtripClient_0() *CommNtripClient {
	rval := &CommNtripClient{
		flag:      0,
		client:    gnss.NewNtripClient(),
		listeners: utils.NewSafeList(),
	}

	rval.client.SetOnRecvBuffer(rval.onRecvNtripClientBuffer)
	rval.client.SetOnConnected(rval.onNtripConnected)
	rval.client.SetOnDisconnected(rval.onNtripDisconnected)
	return rval
}

func NewCommNtripClient(addr string, mountpoint string, user string, pass string, gga string) *CommNtripClient {
	rval := NewCommNtripClient_0()

	rval.client.SetRequestMountPoint(mountpoint)
	rval.client.SetRemoteAddress(addr)
	rval.client.SetNtripClientUser(user, pass)
	rval.SetNMEA(gga)

	return rval
}

/*
**

	ntripcltex://USER:PASS@IP:PORT/MOUNTPOINT[/GGA][/config]
*/
func NewCommNtripCltExFromCmd(cmdstr string) (interface{}, error) {
	rawcmdstr := cmdstr
	ok, cmdstr := utils.CheckTrimPrefix(cmdstr, "ntripcltex://")
	if !ok {
		return nil, nil
	}

	strs := strings.SplitN(cmdstr, "/", 4)
	if len(strs) < 2 {
		return nil, fmt.Errorf("非法的命令行参数(%s)", rawcmdstr)
	}
	strConnectString := strs[0]
	strMnt := strs[1]
	strGGA := ""
	strConf := ""
	if len(strs) >= 3 {
		strGGA = strs[2]
	}
	if len(strs) >= 4 {
		strConf, _ = utils.QueryUnescape(strs[3])

	}

	rval := NewCommNtripClient_0()
	rval.SetConnectString(strConnectString)
	rval.SetNMEA(strGGA)
	rval.SetConfig(strConf)
	rval.client.SetRequestMountPoint(strMnt)
	rval.SetTrimNtripShake(1)
	return rval, nil
}

func (this *CommNtripClient) CheckOnlineOfflineInterval() bool {
	if this.config_online_m <= 0 {
		return true
	}
	if this.config_offline_m <= 0 {
		this.config_offline_m = 60 - this.config_online_m
	}

	n := time.Now().Unix() / 60                              // 分钟Unix分钟数
	n1 := n % (this.config_online_m + this.config_offline_m) // 取余数
	return n1 < this.config_online_m                         // 如果在连接时间内,可以连接
}

func (this *CommNtripClient) CheckCanConnect() bool {
	if this.config_timesection != nil && this.config_timesection.Count() > 0 {
		//str := time.Now().Format("15:04")
		// 判断是否在时间段中
		r := len(this.config_timesection.IsNowIn()) > 0
		return r && this.CheckOnlineOfflineInterval()
	} else {
		if this.config_start == 0 || this.config_end == 0 {
			return this.CheckOnlineOfflineInterval()
		}

		startOfDay := utils.StartOfADay(time.Now())
		if time.Now().After(startOfDay.Add(this.config_start)) &&
			time.Now().Before(startOfDay.Add(this.config_end)) {
			return this.CheckOnlineOfflineInterval()
		} else {
			return false
		}

	}

}

func (this *CommNtripClient) SetConfig(conf string) {
	if len(conf) < 2 {
		return
	}

	json, err := wrapper.NewValueFromJson([]byte(conf), false, false)
	if err != nil {
		utils.Log.Warnf("配置是非法的JSON:%s", err.Error())
		return
	}
	if json == nil {
		utils.Log.Warnf("配置是非法的JSON")
		return
	}

	strUserAgent := json.AsString("useragent", "")
	if len(strUserAgent) > 0 {
		this.SetUserAgent(strUserAgent)
	}

	this.config_rand_m = json.AsDouble("rand", 0)
	this.config_rand_sec = 0
	if this.config_rand_m > 0 {
		this.config_rand_sec = json.AsInt("rand-sec", 10*60)
	}

	this.config_start, err = time.ParseDuration(json.AsString("start", ""))
	this.config_end, err = time.ParseDuration(json.AsString("end", ""))
	this.config_timesection = nil
	this.config_online_m = json.AsInt64("online-m", 0)
	this.config_offline_m = json.AsInt64("offline-m", 0)
	utils.Log.Infof("[%s]:online-m:%d, offline-m:%d", this.client.String(), this.config_online_m, this.config_offline_m)
	if this.config_start > 0 {
		this.doReRandWorkTime()
		this.client.TcpClient.BeforeConnectCb = func(client *dnet.ASyncTcpClientEx) interface{} {
			rval := this.CheckCanConnect()
			if rval {
				utils.Log.Debugf("[%s]工作时段, 允许连接!", this.client.String())
			}
			return rval
		}
	} else {
		str := json.AsString("timesection", "")
		if len(str) == 0 {
			str = json.AsString("timesections", "")
		}
		if len(str) > 0 {
			sec := utils.NewMultiTimeSection(str)
			if this.config_rand_sec > 0 {
				sec.AddRandSecForStart(this.config_rand_sec)
				sec.AddRandSecForEnd(this.config_rand_sec)
			}
			utils.Log.Infof("[now:%v]设定多工作时间段:%s", utils.TimeInLocalToday(time.Now()), sec.String())
			this.config_timesection = sec
			this.client.TcpClient.BeforeConnectCb = func(client *dnet.ASyncTcpClientEx) interface{} {
				rval := this.CheckCanConnect()
				if rval {
					utils.Log.Debugf("[%s]工作时段, 允许连接!", this.client.String())
				}
				return rval
			}
		}
	}

	this.configGen(json)

}

func (this *CommNtripClient) configGen(json *wrapper.SuperValue) bool {
	// 路径点 (推荐使用闭环)
	genpath := json.AsString("gen-path", "")
	gen_speed := json.AsDouble("gen-speed", 100) //
	if len(genpath) == 0 {
		return false
	}

	var pointText string
	if utils.FileIsExists(genpath) {
		buf, err := utils.ReadFile(genpath)
		if err != nil {
			utils.Log.Warnf("gen-path文件(%s)读取异常: %s", genpath, err.Error())
			return false
		}
		pointText = string(buf)
	} else if strings.HasPrefix(genpath, "http") {
		resp, err := http.Get(genpath)
		if err != nil {
			utils.Log.Warnf("gen-path Http读取(%s)读取异常:%s", genpath, err.Error())
			return false
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			utils.Log.Warnf("gen-path Http(%s), 解析Body异常:%s", genpath, err.Error())
			return false
		}
		pointText = string(body)
	} else {
		pointText = genpath
	}

	if len(pointText) == 0 {
		utils.Log.Warnf("gen-path 未知的路径:%s, 读取失败", genpath)
		return false
	}

	gen := gis.NewGenPoint(0, func(x1, y1, x2, y2 float64) uint64 {
		dis := gis.CalcuDis(x1, y1, x2, y2)
		T := dis / float64(gen_speed) * 60 // 需要多少秒
		return uint64(T)
	})

	r := gen.AddFxiedPointFromParseText(pointText, nil)
	if r == 0 {
		utils.Log.Warnf("gen-path 未知的路径:%s, 解析路径失败, 无法解析点", genpath)
		return false
	}
	utils.Log.Infof("gen-path 路径解析成功, 全程需要:%s", utils.HumanTimeStr(int64(gen.MaxT())))

	this.config_gen = gen
	return true
}

func (this *CommNtripClient) SetNMEA(gga string) bool {
	if this.flag == 1 {
		return false
	}

	if len(gga) > 0 {
		strs := strings.Split(gga, ",")
		if len(strs) == 2 {
			nmea := gis.NewNMEA()
			nmea.ParseText("$GNGGA,010918.00,3025.36715396,N,11427.95302424,E,2,12,0.9,28.743,M,-13.869,M,85.0,0129*71")
			nmea.SetLng(utils.StrToFloat64Def(strs[0], 0))
			nmea.SetLat(utils.StrToFloat64Def(strs[1], 0))
			nmea.UpdateUTCTime()
			strNmea := nmea.Encode()
			utils.Log.Infof("[%s]经纬度转换GGA:%s", this.client.String(), strNmea)
			this.nmea = nmea
		} else {
			m := gis.NewNMEA()
			m.ParseText(gga)
			this.nmea = m
		}

		this.config_lng_0 = this.nmea.GetLng()
		this.config_lat_0 = this.nmea.GetLat()
		this.config_height_0 = utils.StrToFloat64Def(this.nmea.Height, 0)
	} else {
		this.nmea = nil
	}

	return true
}

func (this *CommNtripClient) SetUserAgent(val string) bool {
	if this.flag == 1 {
		return false
	}
	this.client.SetUserAgent(val)
	return true
}

func (this *CommNtripClient) SetHost(remoteAddr string) (changed bool) {
	return this.client.SetRemoteAddress(remoteAddr)
}

func (this *CommNtripClient) SetNtripClientUser(remoteAddr string) (changed bool) {
	return this.client.SetRemoteAddress(remoteAddr)
}

func (this *CommNtripClient) SetTrimNtripShake(v byte) bool {
	return this.client.SetTrimNtripShake(v)
}

/**
 * USER:PASS@IP:PORT
 */
func (this *CommNtripClient) SetConnectString(connString string) bool {
	if this.flag == 1 {
		return false
	}

	strAddr := connString
	idx := strings.LastIndex(connString, "@")
	if idx != -1 {
		strAuth := connString[:idx]
		strs := strings.SplitN(strAuth, ":", 2)
		if len(strs) == 2 {
			this.client.SetNtripClientUser(strs[0], strs[1])
		} else {
			this.client.SetNtripClientUser(strs[0], "")
		}

		strAddr = connString[idx+1:]
	} else {
		this.client.SetNtripClientUser("", "")
	}

	this.client.SetRemoteAddress(strAddr)

	return true

}

func (this *CommNtripClient) checkSend() {
	if this.flag == 0 {
		return
	}

	if this.client.GetState() == 1 {
		if this.nmea != nil {
			gen := this.config_gen
			if gen != nil {
				x, y, ok := gen.GenPointNowUnixSec()
				if ok {
					if this.config_rand_m > 0 {
						this.nmea.SetLat(gis.OffsetLatMetre(y, utils.RandFloat64(this.config_rand_m)))
						this.nmea.SetLng(gis.OffsetLngMetre(x, utils.RandFloat64(0.5)))
						this.nmea.DGPSAge = fmt.Sprintf("%d", 1+rand.Intn(2))
						this.nmea.HDOP = fmt.Sprintf("%.1f", 0.9+utils.RandFloat64(0.5))
					}
					if this.config_height_0 != 0 {
						this.nmea.Height = fmt.Sprintf("%.3f", this.config_height_0+utils.RandFloat64(0.05))
						this.nmea.HeightGeoid = "0.00"
					}
					this.nmea.UpdateUTCTime()
					s := this.nmea.Encode()
					utils.Log.Debugf("[%s]改变NMEA:%.6f %.6f, %s M, NMEA:%s", this.client.String(), this.nmea.GetLng(), this.nmea.GetLat(), this.nmea.Height, s)
				}
			} else if this.config_rand_m > 0 {
				if this.config_change_T.IsZero() ||
					time.Now().Sub(this.config_change_T).Seconds() > utils.RandFloat64(60) {
					this.nmea.SetLat(gis.OffsetLatMetre(this.config_lat_0, utils.RandFloat64(this.config_rand_m)))
					this.nmea.SetLng(gis.OffsetLngMetre(this.config_lng_0, utils.RandFloat64(2)))
					this.nmea.DGPSAge = fmt.Sprintf("%d", 1+rand.Intn(2))
					this.nmea.HDOP = fmt.Sprintf("%.1f", 0.9+utils.RandFloat64(0.5))
					if this.config_height_0 != 0 {
						this.nmea.Height = fmt.Sprintf("%.3f", this.config_height_0+utils.RandFloat64(0.05))
						this.nmea.HeightGeoid = "0.00"
					}
					this.config_change_T = time.Now()
					this.nmea.UpdateUTCTime()
					s := this.nmea.Encode()
					utils.Log.Debugf("[%s]改变NMEA:%.6f %.6f, %s M, NMEA:%s", this.client.String(), this.nmea.GetLng(), this.nmea.GetLat(), this.nmea.Height, s)
				}
			}
			this.nmea.UpdateUTCTime()
			s := this.nmea.Encode() + "\r\n"
			this.client.PostBuffer([]byte(s))
		}
	}
}

// =================================================================
func (this *CommNtripClient) onRecvNtripClientBuffer(sender *gnss.NtripClient, buffer []byte) {
	this.listeners.ForEach(func(val interface{}) {
		val.(I_CommListener).OnReceive(this, buffer, len(buffer))
	})
}

func (this *CommNtripClient) onNtripConnected(sender *gnss.NtripClient) {
	this.listeners.ForEach(func(val interface{}) {
		val.(I_CommListener).OnConnect(this)
	})
}

func (this *CommNtripClient) onNtripDisconnected(sender *gnss.NtripClient) {
	utils.Log.Infof("[%s]断开连接:%s", sender.String(), sender.TcpClient.LastDisMsg)
	this.listeners.ForEach(func(val interface{}) {
		val.(I_CommListener).OnDisconnect(this)
	})
}

// =================================================================
func (this *CommNtripClient) Start() {
	this.client.Open()
	this.flag = 1
	this.taskTimer = time.AfterFunc(time.Second*5, this.doIntervalAction)
}

func (this *CommNtripClient) doReRandWorkTime() {
	if this.config_start > 0 {
		this.config_start = this.config_start + time.Second*time.Duration(rand.Intn(this.config_rand_sec))
		this.config_end = this.config_end + time.Second*time.Duration(rand.Intn(this.config_rand_sec))
		startOfDay := utils.StartOfADay(time.Now())
		utils.Log.Infof("[%s]工作时间: %s-%s",
			this.client.String(),
			utils.DateTimeString2(startOfDay.Add(this.config_start)),
			utils.DateTimeString2(startOfDay.Add(this.config_end)))
	}
}

func (this *CommNtripClient) doIntervalAction() {
	if this.flag == 0 {
		return
	}

	if this.CheckCanConnect() {
		if this.client.TcpClient.CheckBreakConnectIfRecvTimeOut(30) {
			utils.Log.Infof("[%s]超时无数据返回断开连接", this.client.String())
		}
		this.checkSend()
	} else {
		if this.client.TcpClient.Connected() {
			utils.Log.Infof("[%s]断开连接, 超出工作时段!", this.client.String())
			this.client.TcpClient.RequestDisconnect("断开连接, 超出工作时段!")
		}
	}

	this.taskTimer = time.AfterFunc(time.Second*10, this.doIntervalAction)
}

func (this *CommNtripClient) Stop() {
	this.client.Close()
	this.flag = 0
	if this.taskTimer != nil {
		this.taskTimer.Stop()
		this.taskTimer = nil
	}
}

func (this *CommNtripClient) GetFromAddr() string {
	return this.client.TcpClient.RemoteAddress
}

func (this *CommNtripClient) GetDisInfo() string {
	return this.client.TcpClient.LastDisMsg
}

func (this *CommNtripClient) GetSendBytes() int64 {
	return this.client.TcpClient.SendBytes()
}

/**
 * 异步发送
 */
func (this *CommNtripClient) Send(buf []byte, len int) int {
	//strNmea := string(buf[:len]);
	//utils.Log.Debugf("ntripclient send:%s", strNmea);

	this.client.TcpClient.PostSendBuffer(buf[:len])
	return len
}

func (this *CommNtripClient) AddListener(event I_CommListener) {
	if this.listeners.Exists(event) {
		utils.Log.Warn("I_CommListener已经存在请勿重复侦听")
		return
	}
	this.listeners.Add(event)
}

func (this *CommNtripClient) RemoveListener(event I_CommListener) {
	this.listeners.Remove(event)
}
